package com.haima.sage.bigdata.api.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.haima.sage.bigdata.api.entity.AppSystem;
import com.haima.sage.bigdata.api.entity.auditlog.AuditLog;
import com.haima.sage.bigdata.api.frame.SpringContextUtil;
import com.haima.sage.bigdata.api.service.AppSystemService;
import com.haima.sage.bigdata.api.service.AuditLogService;
import com.haima.sage.bigdata.api.vo.AuditLogVO;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

@RestController
public class AuditLogController extends BaseController {
    private static final Logger LOG = LogManager.getLogger(AuditLogController.class);
    private final AppSystemService appSystemService;

    @Value("${com.haima.bigdata.query.api.app.id.manage.center}")
    private String manageCenterAppId;
    private Long maxPeriod = 30 * 24 * 60 * 60 * 1000L;

    @Autowired
    public AuditLogController(AuditLogService auditLogService, AppSystemService appSystemService) {
        super(auditLogService);
        this.appSystemService = appSystemService;
    }

    @RequestMapping("/api/auditlog/{id}")
    public ResponseEntity<String> getOne(HttpServletRequest req,
                                         @PathVariable("id") String id,
                                         @RequestParam("appkey") String appKey,
                                         @RequestParam("sign") String sign,
                                         @RequestParam("timestamp") Long timestamp,
                                         @RequestParam("appUserId") String appUserId,
                                         @RequestParam("appUserName") String appUserName) {
        // 可以访问此接口的业务系统
        AppSystem bs = null;
        try {
            bs = isForbidden(appKey);
            if (bs == null) {
                return error(HttpStatus.FORBIDDEN,
                        "no power to access API", "没有权限访问此API");
            }
        } catch (Exception e) {
            return error(HttpStatus.INTERNAL_SERVER_ERROR,
                    "business system secret descrypt fail", "业务系统密钥解密失败");

        }

        if (!validSign(bs.getSecret(), req.getRequestURI(),
                null, appKey, timestamp, sign, appUserId, appUserName)) {
            return error(HttpStatus.BAD_REQUEST,
                    "sign failure", "签名错误");
        }
        if (System.currentTimeMillis() - timestamp > timeLimit) {
            return error(HttpStatus.BAD_REQUEST,
                    "time failure", "时间失效");
        }
        // 此日志是否存在
        Optional<AuditLog> optional = auditLogService.findById(id);
        if (optional.isPresent()) {
            AuditLog api = optional.get();
            return ok(api);
        } else {
            return error(HttpStatus.NOT_FOUND, "not found", "无此API");
        }
    }

    @PostMapping(value = "/api/auditlog")
    public ResponseEntity<String> search(HttpServletRequest req,
                                         @RequestParam("sign") String sign,
                                         @RequestBody String json) {
        ObjectMapper mapper = new ObjectMapper();
        AuditLogVO vo = null;
        try {
            vo = mapper.readValue(json, AuditLogVO.class);
        } catch (IOException e) {
            return error(HttpStatus.BAD_REQUEST,
                    "input json format error", "输入的JSON格式错误");
        }
        // 可以访问此接口的业务系统
        AppSystem bs = null;
        try {
            bs = isForbidden(vo.getAppkey());
        } catch (Exception e) {
            return error(HttpStatus.INTERNAL_SERVER_ERROR,
                    "business system secret descrypt fail", "业务系统密钥解密失败");
        }
        if (bs == null) {
            return error(HttpStatus.FORBIDDEN,
                    "no power to access API", "没有权限访问此API");
        }
        Long timestamp = Long.parseLong(vo.getTimestamp());
        if (!validSign(bs.getSecret(), req.getRequestURI(), json, sign)) {
            return error(HttpStatus.BAD_REQUEST,
                    "sign failure", "签名错误");
        }
        if (System.currentTimeMillis() - timestamp > timeLimit) {
            return error(HttpStatus.BAD_REQUEST,
                    "time failure", "时间失效");
        }
        if (vo.getBeginDate() == null || vo.getEndDate() == null) {
            return error(HttpStatus.BAD_REQUEST,
                    "beginDate and endDate must not empty", "参数beginDate、endDate不能为空");
        }
        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
        Timestamp beginTime = parse(sdf1, sdf2, vo.getBeginDate());
        Timestamp endTime = parse(sdf1, sdf2, vo.getEndDate());
        long period = endTime.getTime() - beginTime.getTime();
        LOG.debug("period:{}, max:{}", period, maxPeriod);
        if (period > maxPeriod) {
            return error(HttpStatus.BAD_REQUEST,
                    "time period must less than 30 days", "查询时间段必须小于30天");
        }
        int from;
        try {
            from = Integer.parseInt(vo.getFrom());
        } catch (Exception e) {
            from = 0;
        }
        int size;
        try {
            size = Integer.parseInt(vo.getSize());
        } catch (Exception e) {
            size = 10;
        }

        int pageIndex = from / size;
        Specification<AuditLog> query = buildSpecification(vo, beginTime, endTime);
        Pageable pageable = PageRequest.of(pageIndex, size);
        Page<AuditLog> pages = this.auditLogService.findAll(query, pageable);
        Map<String, Object> map = new HashMap<>();
        map.put("content", pages.iterator());
        map.put("elements", pages.getTotalElements());
        return ok(map);
    }


    private AppSystem isForbidden(String appId) throws Exception {
        if (!manageCenterAppId.equalsIgnoreCase(appId)) {
            return null;
        }
        Optional<AppSystem> bs = appSystemService.findById(manageCenterAppId);
        if (bs.isPresent()) {
            AppSystem tmp = bs.get();
            decryptSecret(tmp);
            return tmp;
        }
        return null;
    }

    public Specification<AuditLog> buildSpecification(AuditLogVO vo, Timestamp beginTime, Timestamp endTime) {
        return new Specification<AuditLog>() {
            private static final long serialVersionUID = 1L;

            @Override
            public Predicate toPredicate(Root<AuditLog> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                List<Predicate> predicates = new ArrayList<>();
                predicates.add(cb.greaterThanOrEqualTo(root.get("createDate"), beginTime));
                predicates.add(cb.lessThan(root.get("createDate"), endTime));
                if (!StringUtils.isEmpty(vo.getName())) {
                    predicates.add(cb.like(root.get("name").as(String.class), "%" + vo.getName() + "%"));
                }
                if (!StringUtils.isEmpty(vo.getContent())) {
                    predicates.add(cb.like(root.get("content").as(String.class), "%" + vo.getContent().trim() + "%"));
                }

                if (!StringUtils.isEmpty(vo.getAppId())) {
                    predicates.add(cb.equal(root.get("appId").as(String.class), vo.getAppId()));
                }
                if (!StringUtils.isEmpty(vo.getAppName())) {
                    predicates.add(cb.like(root.get("appName").as(String.class), "%" + vo.getAppName() + "%"));
                }
                if (!StringUtils.isEmpty(vo.getUserId())) {
                    predicates.add(cb.equal(root.get("userId").as(String.class), vo.getUserId()));
                }
                if (!StringUtils.isEmpty(vo.getUserName())) {
                    predicates.add(cb.like(root.get("userName").as(String.class), "%" + vo.getUserName() + "%"));
                }

                return cb.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        };
    }

    private Timestamp parse(SimpleDateFormat sdf1, SimpleDateFormat sdf2, String str) {
        if (StringUtils.isEmpty(str)) {
            return null;
        }
        try {
            return new Timestamp(sdf1.parse(str).getTime());
        } catch (ParseException e) {
        }
        try {
            return new Timestamp(sdf2.parse(str).getTime());
        } catch (ParseException e) {
            e.printStackTrace();
        }
        try {
            return new Timestamp(Long.parseLong(str));
        } catch (Exception e) {
        }
        throw new IllegalArgumentException("date format error:" + str);
    }
}
